package org.bing.engine.common.remoting.hessian;

import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.io.PrintWriter;

import javax.servlet.ServletConfig;
import javax.servlet.ServletException;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import org.bing.adapter.com.caucho.hessian.io.SerializerFactory;
import org.bing.adapter.com.caucho.services.server.ServiceContext;
import org.bing.engine.common.logging.Log;
import org.bing.engine.common.logging.LogFactory;
import org.bing.engine.utility.helper.StringHelper;
import org.springframework.context.ApplicationContext;
import org.springframework.web.context.WebApplicationContext;

public class HessianServlet extends org.bing.adapter.com.caucho.hessian.server.HessianServlet {
    private static final long serialVersionUID = 1978L;
    private static final Log logger = LogFactory.getLog(HessianServlet.class);
    protected ApplicationContext context;

    @Override
    public void init(ServletConfig config) throws ServletException {
        // ignore super, init spring
        if (context == null) {
            context = (ApplicationContext) config.getServletContext().getAttribute(WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE);
        }
        if (context == null) {
            logger.error("Can't find spring context from ServletContext!");
        } else {
            logger.info("Init hessian servlet complete.");
        }
    }

    @Override
    public void service(ServletRequest request, ServletResponse response) throws IOException, ServletException {
        HttpServletRequest req = (HttpServletRequest) request;
        HttpServletResponse res = (HttpServletResponse) response;
        Object service = getService(req, res);
        if (!req.getMethod().equals("POST")) {
            res.setStatus(500);
            PrintWriter out = res.getWriter();
            res.setContentType("text/html");
            out.println("<h1>Hessian Requires POST!</h1>");
            return;
        }
        if (service == null) {
            res.setStatus(500);
            PrintWriter out = res.getWriter();
            res.setContentType("text/html");
            out.println("<h1>Can't find service from " + req.getRequestURI() + "!</h1>");
            logger.warn("Can't find service from " + req.getRequestURI());
            return;
        }
        // handle hessian request
        String serviceId = req.getPathInfo();
        String objectId = req.getParameter("id");
        if (objectId == null)
            objectId = req.getParameter("ejbid");
        ServiceContext.begin(req, serviceId, objectId);
        try {
            InputStream is = request.getInputStream();
            OutputStream os = response.getOutputStream();
            response.setContentType("application/x-hessian");
            SerializerFactory serializerFactory = getSerializerFactory();
            invoke(is, os, objectId, service, serializerFactory);
        } catch (Throwable e) {
            logger.error("Fail to handle hessian request!", e);
            throw new ServletException(e);
        } finally {
            ServiceContext.end();
        }
    }

    protected void invoke(InputStream is, OutputStream os, String objectId, Object object, SerializerFactory serializerFactory) throws Exception {
        // hessian 4.0.7, not support 3.x.x
        HessianSkeleton skeleton = new HessianSkeleton(object, object.getClass());
        skeleton.invoke(is, os, serializerFactory);
    }

    protected Object getService(HttpServletRequest req, HttpServletResponse res) {
        // spring* [ http://host:port/controller/hessian/{BeanId}${InterfaceClazz} ]
        // message [ http://host:port/controller/hessian/{BeanId}${InterfaceClazz} ]
        String uri = req.getRequestURI();
        String sid = uri.substring(uri.lastIndexOf("/") + 1);
        if (!StringHelper.isBlank(uri)) {
            if (sid.startsWith("$")) {
                String clazz = sid.substring(1);
                try {
                    String[] beanIds = context.getBeanNamesForType(Class.forName(clazz));
                    if (beanIds != null && beanIds.length == 1) {
                        return getService(beanIds[0]);
                    } else {
                        logger.warn("Can't locate bean from " + sid + ", " + (beanIds == null ? 0 : beanIds.length));
                    }
                } catch (ClassNotFoundException e) {
                    logger.error("Class not found!", e);
                }
            } else {
                String[] ss = sid.split("\\$");
                if (ss.length == 2) {
                    String beanId = ss[0];
                    return getService(beanId);
                } else {
                    logger.warn("Can't locate bean from " + sid);
                }
            }
        }
        return null;
    }

    private Object getService(String beanId) {
        try {
            return context.getBean(beanId);
        } catch (Exception e) {
            // ignore
        }
        return null;
    }
}
